package yb.ecp.fast.infra.security;

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import yb.ecp.fast.infra.bus.GuavaBus;
import yb.ecp.fast.infra.infra.CommonUtil;

import javax.annotation.Resource;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.atomic.AtomicReference;


@Service
@RefreshScope
@Slf4j
public class PreprocessRequestFilter implements GlobalFilter, Ordered {
    @Resource(name = "gwRedisTemplate")
    private RedisTemplate redisTemplate;

    private static final String TOKEN_KEY = CommonUtil.DEFAULT_TOKEN_KEY;
    private static final String USER_ID_KEY = CommonUtil.DEFAULT_X_USER_ID_KEY;
    private static final String ACCESS_CLIENT = "x-access-client";

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        return getToken(exchange)
                .flatMap(token -> validateToken(exchange, token))
                .switchIfEmpty(handleNoToken(exchange))
                .flatMap(chain::filter);
    }

    private Mono<String> getToken(ServerWebExchange exchange) {
        return Mono.justOrEmpty(exchange.getRequest().getHeaders().getFirst(TOKEN_KEY))
                .switchIfEmpty(Mono.justOrEmpty(exchange.getRequest().getQueryParams().getFirst(TOKEN_KEY)));
//                .switchIfEmpty(getFormToken(exchange));
    }

    private Mono<String> getFormToken(ServerWebExchange exchange) {

        if ("application/x-www-form-urlencoded".equals(exchange.getRequest().getHeaders().getContentType().toString())) {
            Flux<DataBuffer> body = exchange.getRequest().getBody();
            AtomicReference<String> bodyRef = new AtomicReference<>();
            return body.collectList()
                    .flatMap(list -> {
                        DataBuffer dataBuffer = list.get(0);
                        bodyRef.set(dataBuffer.toString(StandardCharsets.UTF_8));
                        // 释放数据缓冲区
                        DataBufferUtils.release(dataBuffer);
                        return Mono.just(bodyRef.get());
                    })
                    .flatMap(bodyStr -> {
                        // 解析请求体
                        MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
                        String[] pairs = bodyStr.split("&");
                        try {
                            for (String pair : pairs) {
                                int idx = pair.indexOf("=");
                                String key = idx > 0 ? URLDecoder.decode(pair.substring(0, idx), "UTF-8") : pair;
                                String value = idx > 0 && pair.length() > idx + 1 ? URLDecoder.decode(pair.substring(idx + 1), "UTF-8") : null;
                                formData.add(key, value);
                            }
                        } catch (Exception e) {
                            log.error("Token retrieval failed", e);
                        }
                        return Mono.justOrEmpty(formData.getFirst(TOKEN_KEY));
                    });
        } else
            return exchange.getFormData()
                    .flatMap(formData -> Mono.justOrEmpty(formData.getFirst(TOKEN_KEY)))
                    .onErrorResume(e -> {
                        log.error("Token retrieval failed", e);
                        return Mono.empty();
                    });
    }

    private Mono<ServerWebExchange> validateToken(ServerWebExchange exchange, String token) {
        return Mono.fromCallable(() -> redisTemplate.opsForValue().get(CommonUtil.genTokenKey(token)))
                .map(userId -> {
                    ServerHttpRequest newRequest = exchange.getRequest().mutate()
                            .header(USER_ID_KEY, (String) userId)
                            .header(ACCESS_CLIENT, "gateway")
                            .build();
                    GuavaBus.post(CommonUtil.genTokenKey(token));
                    return exchange.mutate().request(newRequest).build();
                })
                .onErrorResume(e -> {
                    log.error("Token validation failed", e);
                    ServerHttpRequest newRequest = exchange.getRequest().mutate()
                            .header(USER_ID_KEY, "")
                            .header(ACCESS_CLIENT, "gateway")
                            .build();
                    return Mono.just(exchange.mutate().request(newRequest).build());
                });
    }

    private Mono<ServerWebExchange> handleNoToken(ServerWebExchange exchange) {
        ServerHttpRequest newRequest = exchange.getRequest().mutate()
                .header(USER_ID_KEY, "")
                .header(ACCESS_CLIENT, "gateway")
                .build();
        return Mono.just(exchange.mutate().request(newRequest).build());
    }

    @Override
    public int getOrder() {
        return -2;
    }

}
